package com.chen.service.impl;

import cn.hutool.core.lang.Snowflake;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chen.domain.Account;
import com.chen.domain.AccountDetail;
import com.chen.domain.Coin;
import com.chen.dto.AccountDto;
import com.chen.dto.EthereumAddressDto;
import com.chen.dto.MarketDto;
import com.chen.dto.UserAddressDto;
import com.chen.enums.AccountEnums;
import com.chen.enums.DirectionEnums;
import com.chen.fegin.MarketFegin;
import com.chen.fegin.UserAddressFegin;
import com.chen.mapper.AccountDetailMapper;
import com.chen.mapper.AccountMapper;
import com.chen.model.AccountVo;
import com.chen.model.SymbolAssetVo;
import com.chen.model.UserTotalAccountVo;
import com.chen.service.AccountDetailService;
import com.chen.service.AccountService;
import com.chen.service.CoinService;
import com.chen.service.RecordsService;
import com.chen.utils.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {

    private static final String ADDRESS_KEY = "ADDRESS_KEY";

    @Resource
    private MarketFegin marketFegin;

    @Resource
    private UserAddressFegin userAddressFegin;

    private final Snowflake snowflake = new Snowflake(2, 1);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RecordsService<Account> recordsService;

    @Autowired
    private CoinService coinService;

    @Resource
    private AccountMapper accountMapper;

    @Resource
    private AccountDetailMapper accountDetailMapper;

    @Autowired
    private AccountDetailService accountDetailService;


    @Override
    public Page<Account> findPageForList(Page<Account> page, Long userId, String userName, String mobile, Long coinId, String startAmount, String endAmount, Byte status) {
        try {
            RecordsServiceImpl.SearchRecords searchRecords = new RecordsServiceImpl.SearchRecords();
            searchRecords.setCoinId(coinId);
            searchRecords.setUserId(userId);
            searchRecords.setUserName(userName);
            searchRecords.setMobile(mobile);
            Map<RecordsServiceImpl.SearchRecords.BetweenDataEnum, Map<String, RecordsServiceImpl.SearchRecords.LimitRecords>> betweenData = new HashMap<>();

            //设置金钱区间
            Map<String, RecordsServiceImpl.SearchRecords.LimitRecords> limitRecordsMap = new HashMap<>();
            limitRecordsMap.put("balance_amount", RecordsServiceImpl.SearchRecords.LimitRecords.builder().min(startAmount).max(endAmount).build());
            betweenData.put(RecordsServiceImpl.SearchRecords.BetweenDataEnum.AMOUNT, limitRecordsMap);

            searchRecords.setBetweenData(betweenData);
            return recordsService.findData(page, accountMapper, searchRecords);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transfer(Long coinFrom, Long coinTo, Long userFrom, Long userTo, Long orderId, String businessType, BigDecimal amount, BigDecimal fee, String remark) {

        QueryWrapper<Account> queryWrapper;

        Account accountTo = null;
        log.info("获取账户开始");
        accountTo = findAccout(coinTo, userTo, accountTo);

        Account accountFrom = null;
        accountFrom = findAccout(coinFrom, userFrom, accountFrom);
        log.info("获取账户结束，开始转账");

        //确认来源的人钱够不够
        checkAmount(accountFrom, amount, fee);

        //account to
        accountTr(accountTo, accountFrom, coinTo, userTo, orderId, businessType, amount, fee, remark, DirectionEnums.IN.getCode());
        //account from
        accountTr(accountFrom, accountTo, coinFrom, userFrom, orderId, businessType, amount, fee, remark, DirectionEnums.OUT.getCode());
        log.info("转账结束");
    }

    @Override
    public Account findUserAccountByCoinName(Long coinId, String gcn, Long userId) {

        Object buyObject = redisTemplate.opsForHash().get("CNY", "CNY2USDT");
        if (buyObject == null) {
            throw new IllegalArgumentException("请设置人民币充值GCN换算费率");
        }
        Object sellObject = redisTemplate.opsForHash().get("CNY", "USDT2CNY");

        if (sellObject == null) {
            throw new IllegalArgumentException("请设置人民币提现GCN换算费率");
        }
        Coin coin = new Coin();
        if (coinId == null) {
            coin = coinService.findCoinByName(gcn);
        } else {
            coin.setId(coinId);
        }
        QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("coin_id", coin.getId());
        queryWrapper.eq("user_id", userId);
        List<Account> list = list(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            Account account = buildAccount(coin.getId(), userId);
            save(account);
            account.setBuyRate(new BigDecimal(buyObject.toString()));
            account.setSellRate(new BigDecimal(sellObject.toString()));
            return account;
        }
        Account account = list.get(0);
        account.setBuyRate(new BigDecimal(buyObject.toString()));
        account.setSellRate(new BigDecimal(sellObject.toString()));
        return account;
    }

    @Override
    public SymbolAssetVo findSymbolAccount(String symbol) {

        // LTC BTC  LTC coinId userId -》 account
        //根据LTCBTC查询币种id和手续费
        MarketDto marketDto = marketFegin.getMarketBySymbol(symbol);
        if (marketDto == null) {
            throw new IllegalArgumentException("请先设置交易对标识");
        }
        Long userId = UserUtil.getUser();
        Account buyAccount = findUserAccountByCoinName(marketDto.getBuyCoinId(), null, userId);
        Account sellAccount = findUserAccountByCoinName(marketDto.getSellCoinId(), null, userId);
        SymbolAssetVo symbolAssetVo = new SymbolAssetVo();

        Coin buyCoin = coinService.getById(buyAccount.getCoinId());
        Coin sellCoin = coinService.getById(sellAccount.getCoinId());

        symbolAssetVo.setBuyAmount(buyAccount.getBalanceAmount());
        symbolAssetVo.setBuyFeeRate(marketDto.getFeeBuy());
        symbolAssetVo.setBuyLockAmount(buyAccount.getFreezeAmount());
        symbolAssetVo.setBuyUnit(buyCoin.getName());

        symbolAssetVo.setSellAmount(sellAccount.getBalanceAmount());
        symbolAssetVo.setSellFeeRate(marketDto.getFeeSell());
        symbolAssetVo.setSellLockAmount(sellAccount.getFreezeAmount());
        symbolAssetVo.setSellUnit(sellCoin.getName());

        return symbolAssetVo;
    }

    @Override
    public Boolean transferUserLock(Long coinId, Long userId, BigDecimal amount, Long orderId) {

        Account account = findUserAccountByCoinName(coinId, null, userId);
        if (account.getBalanceAmount().compareTo(amount) < 0) {
            return false;
        }
        account.setBalanceAmount(account.getBalanceAmount().subtract(amount));
        account.setFreezeAmount(account.getFreezeAmount().add(amount));
        updateById(account);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserTotalAccountVo findTotal() {

        UserTotalAccountVo userTotalAccountVo = new UserTotalAccountVo();


        //检查用户账户表中的数据与现有的币种表中的数据是否一直，如果不一致，则按照币种表中的数据创建账户
        List<Coin> coinList = coinService.list(
                new LambdaQueryWrapper<Coin>().eq(Coin::getWallet, "qbb"));

        //提取币种表中的coinId
        List<Long> coinIds = coinList.stream().
                map(Coin::getId).collect(Collectors.toList());

        //查询用户下面所有的币种的账户余额
        List<Account> accounts = accountMapper.selectList(new LambdaQueryWrapper<Account>()
                .in(Account::getCoinId, coinIds)
                .eq(Account::getUserId, UserUtil.getUser()));

        List<Long> coinIdX = new ArrayList<>();
        for (Coin coin : coinList) {
            for (Account account : accounts) {
                if (coin.getId().equals(account.getCoinId())) {
                    coinIdX.add(coin.getId());
                }
            }
        }
        //重新构造一个list用户做判断 账户中的币种 是否和 币种表中的币种一致  ，不一致 取差集
        ArrayList<Long> longs = new ArrayList<>(coinIds);
        longs.removeAll(coinIdX);

        if (!CollectionUtils.isEmpty(longs)) {
            //批量保存用户的账户数据【初始化账户】
            List<Account> account = saveAccounts(longs, UserUtil.getUser());
            accounts.addAll(account);
        }

        //提取币种表中的交易对数据
        List<String> symbol = coinList.stream().map(coin -> coin.getName().toUpperCase() + "GCN")
                .collect(Collectors.toList());

        //查询交易市场
        List<MarketDto> marketBySymbols = marketFegin.getMarketBySymbols(symbol);

        BigDecimal total = BigDecimal.ZERO;

        //计算当前账户表中的总余额 -》 GCN计价单位
        for (Coin coin : coinList) {
            for (MarketDto marketDto : marketBySymbols) {
                if (marketDto.getSymbol().contains(coin.getName())) {
                    BigDecimal openPrice = marketDto.getOpenPrice();
                    for (Account account : accounts) {
                        if (account.getCoinId().equals(coin.getId())) {
                            total = total.add(account.getBalanceAmount().multiply(openPrice));
                        }
                    }
                }
            }
        }

        List<AccountVo> accountVos = new ArrayList<>();
        for (Account account : accounts) {
            AccountVo accountVo = new AccountVo();
            BeanUtils.copyProperties(account, accountVo);
            for (Coin coin : coinList) {
                if (coin.getId().equals(account.getCoinId())) {
                    accountVo.setCoinImgUrl(coin.getImg());
                    accountVo.setCoinName(coin.getName());
                    accountVo.setWithdrawFlag(1);
                    accountVo.setRechargeFlag(1);
                    accountVo.setCarryingAmount(account.getBalanceAmount().add(account.getFreezeAmount()));
                }
            }
            accountVos.add(accountVo);
        }
        userTotalAccountVo.setAmount(total);
        userTotalAccountVo.setAmountUs(total);
        userTotalAccountVo.setAmountUsUnit("GCN");
        userTotalAccountVo.setAssertList(accountVos);
        return userTotalAccountVo;
    }

    @Override
    public List<AccountDto> buildUserAccount(Long userId) {

        if(redisTemplate.hasKey("ACCOUNT_LOCK" + userId)){
            return Collections.emptyList();
        }else {
            redisTemplate.opsForValue().set("ACCOUNT_LOCK" + userId,UUID.randomUUID().toString(),60, TimeUnit.SECONDS);
            List<Coin> list = coinService.list();
            List<Long> coinIds = list.stream().map(coin -> coin.getId()).collect(Collectors.toList());
            List<Account> accounts = saveAccounts(coinIds, userId);

            return accounts.stream().map(account -> {
                AccountDto accountDto = new AccountDto();
                BeanUtils.copyProperties(account, accountDto);
                return accountDto;
            }).collect(Collectors.toList());
        }


    }

    private List<Account> saveAccounts(List<Long> coinList, Long userId) {
        List<Account> accounts = new ArrayList<>();
        for (Long coin : coinList) {
            Account account = buildAccount(coin, userId);
            accounts.add(account);
        }
        saveBatch(accounts);
        buildUserAddress(coinList, userId);
        return accounts;
    }

    private void buildUserAddress(List<Long> coin, Long userId) {

        List<Object> range = redisTemplate.opsForList().range(ADDRESS_KEY, 0, coin.size()-1);
        for (int i = 0; i < range.size(); i++) {
            redisTemplate.opsForList().leftPop(ADDRESS_KEY);
        }

        assert range != null;
        List<EthereumAddressDto> transform = range.stream().map(o -> {
            String string = o.toString();
            return JSON.parseObject(string, EthereumAddressDto.class);
        }).collect(Collectors.toList());
        List<UserAddressDto> userAddressDtos = new ArrayList<>();
        for (int i = 0; i < coin.size(); i++) {
            UserAddressDto userAddressDto = new UserAddressDto();
            userAddressDto.setAddress(transform.get(i).getAddress());
            userAddressDto.setCoinId(coin.get(i));
            userAddressDto.setUserId(userId);
            userAddressDto.setKeystore(transform.get(i).getKeystoreName());
            userAddressDtos.add(userAddressDto);
        }
        if(!userAddressFegin.saveUserAddress(userAddressDtos)){
            throw new IllegalArgumentException("创建地址失败");
        };
    }

    private Account buildAccount(Long coinId, Long userId) {
        Account account = new Account();
        account.setUserId(userId);
        account.setCoinId(coinId);
        account.setStatus(1);
        account.setId(snowflake.nextId());
        account.setWithdrawalsAmount(BigDecimal.ZERO);
        account.setLastUpdateTime(new Date());
        account.setRechargeAmount(BigDecimal.ZERO);
        account.setBalanceAmount(BigDecimal.ZERO);
        account.setFloatProfit(BigDecimal.ZERO);
        account.setFreezeAmount(BigDecimal.ZERO);
        account.setLockMargin(BigDecimal.ZERO);
        account.setCreated(new Date());
        account.setNetValue(BigDecimal.ZERO);
        account.setTotalProfit(BigDecimal.ZERO);
        account.setVersion(1l);
//        save(account);
        return account;

    }

    //form 扣减冻结余额 【注意】
    private void checkAmount(Account accountFrom, BigDecimal amount, BigDecimal fee) {
        if (accountFrom != null) {
            if (accountFrom.getFreezeAmount().compareTo(amount.add(fee)) < 0) {
                throw new IllegalArgumentException("账户余额不足");
            }
        }
    }

    private Account findAccout(Long coinFrom, Long userFrom, Account accountFrom) {
        QueryWrapper<Account> queryWrapper;
        if (coinFrom != null && userFrom != null) {
            queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("user_id", userFrom);
            queryWrapper.eq("coin_id", coinFrom);
            queryWrapper.eq("status", 1);
            List<Account> accounts = accountMapper.selectList(queryWrapper);

            if (CollectionUtils.isEmpty(accounts)) {
                throw new IllegalArgumentException("用户的钱包不存在");
            }
            accountFrom = accounts.get(0);
        }
        return accountFrom;
    }

    private void accountTr(Account account, Account accountOther, Long coinId, Long userId, Long orderId, String businessType, BigDecimal amount, BigDecimal fee, String remark, Integer direction) {
        if (account == null) {
            return;
        }
        if (direction == DirectionEnums.IN.getCode()) {
            account.setBalanceAmount(account.getBalanceAmount().add(amount));
        } else {
            //出帐  -  》 冻结余额
            account.setFreezeAmount(account.getFreezeAmount().subtract((amount.add(fee))));
        }
        //如是充值
        if (businessType.equals(AccountEnums.recharge_into.getCode()) && direction == DirectionEnums.IN.getCode()) {
            account.setRechargeAmount(account.getRechargeAmount().add(amount));
        }
        //如是是提现
        if (businessType.equals(AccountEnums.withdrawals_out.getCode()) && direction == DirectionEnums.OUT.getCode()) {
            account.setWithdrawalsAmount(account.getWithdrawalsAmount().add(amount));
        }
        account.setLastUpdateTime(new Date());
        updateById(account);

        //account_detail [增量]
        AccountDetail accountDetail = new AccountDetail();
        accountDetail.setAccountId(account.getId());
        accountDetail.setAmount(amount.add(fee));
        accountDetail.setBusinessType(businessType);
        accountDetail.setCoinId(coinId);
        accountDetail.setCreated(new Date());
        accountDetail.setDirection(direction);
        accountDetail.setFee(fee);
        accountDetail.setId(snowflake.nextId());
        accountDetail.setUserId(userId);
        accountDetail.setRemark(remark);
        accountDetail.setOrderId(orderId);
        if (accountOther != null) {
            accountDetail.setRefAccountId(accountOther.getId());
        }
        accountDetailMapper.insert(accountDetail);
    }
}
